/*
    服务端业务处理模块：将网络通信模块和业务模块进行合并
            1、搭建http网络通信服务器
            2、业务处理
                网络通信接口设计：约定好请求--响应
                1.文件上传：备份客户端上传的文件，响应上传成功
                    当服务器收到一个POST方法的/upload请求，我们则认为这个是一个文件上传请求，
                    解析请求，得到文件数据，将数据写到文件中
                2.展示所有备份文件：响应客户端的文件展示页面请求
                    GET /listshow HTTP/1.1
                3.文件下载：通过展示界面点击下载，响应客户端要下载的文件数据
                    GET /download/... HTTP/1.1
            成员：
                1、服务器端口
                2、服务器ip
                3、下载前缀
                4、httplib服务器对象
            接口：
                1、构造函数
                2、运行
                3、文件上传
                4、展示文件
                5、文件下载
*/

#ifndef __M_SERVICE_H__
#define __M_SERVICE_H__

#include "dataController.hpp"
#include "sup/sup_h/httplib.h"

extern cloud::DataController *_data;

namespace cloud
{
    class Service{
    public:
        Service(){
            Config *config = Config::getInstance();
            _server_port = config->getServerPort();
            _server_ip = config->getServerIP();
            _download_prefix = config->getDownloadPrefix();
        }

        bool run(){
            //由于成员函数有this，所以这里要bind或用lambda绑定，否则就要设置成静态成员函数
            _server.Post("/upload", upload);
            _server.Get("/", list);
            string download_url = _download_prefix + "(.*)";
            // . -- 匹配任意字符    * -- 匹配前面的子表达式任意次   .* -- 匹配任意字符任意字符任意次
            _server.Get(download_url, download);
            _server.listen(_server_ip.c_str(), _server_port);
            return true;
        }

    private:
        static void upload(const httplib::Request &req, httplib::Response &rsp){
            //post /upload 文件数据在正文中
            //1、判断有没有上传的文件字段
            auto ret = req.has_file("file");
            if(ret == false){
                rsp.status = 404;
                cout << "not file upload" << endl;
                return;
            }

            //2、获取文件数据
            const auto &file = req.get_file_value("file");

            //3、获取文件存储路径
            string backup_dir = Config::getInstance()->getBackupDir();
            string fpath = backup_dir + FileUtil(file.filename).fileName();

            //4、将文件数据写入文件
            FileUtil fu(fpath);
            fu.setContent(file.content);

            //5、用数据管理者添加备份的文件信息
            BackupInfo info;
            info.fillNewInfo(fpath);
            _data->insert(info);

            return;
        }

        // static void list(const httplib::Request &req, httplib::Response &rsp){
        //     //1、获取所有文件属性
        //     vector<BackupInfo> arr;
        //     _data->getAll(&arr);

        //     //2、根据获取的属性组织html
        //     stringstream ss;
        //     ss << " <html><head><title>Download</title></head>";
        //     ss << "<body><h1>Download</h1><table>";
        //     for(auto &a : arr){
        //         ss << "<tr>";
        //         //html中不区分双引号和单引号，为了防止双引号的歧义这里替换成单引号
        //         ss << "<td><a href='" << a._url << "'>" << FileUtil(a._fpath).fileName() << "</a></td>";
        //         ss << "<td align=;'right'>" << timefmt(a._lmtime) << "</td>";
        //         ss << "<td align=;'right'>" << sizefmt(a._fsize) << "</td>";
        //         ss << "</tr>";
        //     }
        //     ss << "</table></body></html>";
        //     //3、设置响应消息主体、头部信息、状态
        //     rsp.body = ss.str();
        //     rsp.set_header("Content-Type", "text/html; charset=UTF-8");
        //     rsp.status = 200;
        // }

        static void list(const httplib::Request &req, httplib::Response &rsp){
            //1、获取所有文件属性
            vector<BackupInfo> arr;
            _data->getAll(&arr);

            //2、根据获取的属性组织html
            stringstream ss;
            ss << " <html><head><title>Download</title>";
            // 添加以下样式
            ss << "<style>";
            ss << "body {";
            ss << "  background-color: #f0f0f0;";
            ss << "  font-family: Arial, sans-serif;";
            ss << "}";
            ss << "h1 {";
            ss << "  text-align: center;";
            ss << "  color: #0078d4;";
            ss << "}";
            ss << "table {";
            ss << "  width: 80%;";
            ss << "  margin: 0 auto;";
            ss << "  border-collapse: collapse;";
            ss << "}";
            ss << "th, td {";
            ss << "  padding: 10px;";
            ss << "  border: 1px solid #ccc;";
            ss << "}";
            ss << "th {";
            ss << "  background-color: #0078d4;";
            ss << "  color: #fff;";
            ss << "}";
            
            // 修改以下样式，让表格内容居中对齐
            ss << "td {";
            ss << "  text-align: center;";
            ss << "}";
            
            ss << ".download {";
            ss << "  display: inline-block;";
            ss << "  width: 100px;";
            ss << "  padding: 5px;";
            ss << "  color: #fff;";
            ss << "  background-image: linear-gradient(to right, #0078d4, #00a9f4);";
            ss << "  border: none;";
            ss << "  border-radius: 5px;";
            ss << "  text-align: center;";
            ss << "  text-decoration: none;";
            ss << "}";
            
            ss << ".download:hover {";
            ss << "  background-image: linear-gradient(to right, #005a9e, #0086c3);";
            ss << "}";
            
            // 结束修改
            // 结束样式
            ss << "</style>";
            
            // 修改以下内容
            //ss << "<body><h1>Download</h1><table>";
            
            // 添加表头
            ss << "<body><h1>下载文件</h1><table>";
            
            ss << "<tr>";
            ss << "<th>文件名</th>";
            ss << "<th>修改时间</th>";
            ss << "<th>文件大小</th>";
            ss << "<th>操作</th>"; // 添加一个操作列
            ss << "</tr>";
            
            // 结束表头
            
            for(auto &a : arr){
                ss << "<tr>";
                //html中不区分双引号和单引号，为了防止双引号的歧义这里替换成单引号
                //ss << "<td><a href='" << a._url << "'>" << FileUtil(a._fpath).fileName() << "</a></td>";

                // 修改以下内容，去掉链接，只显示文件名
                //ss << "<td>" << icon << "<a href='" << a._url << "'>" << FileUtil(a._fpath).fileName() << "</a></td>";

                // 只显示文件名
                ss << "<td>" << FileUtil(a._fpath).fileName() << "</td>";

                // 结束修改

                ss << "<td>" << timefmt(a._lmtime) << "</td>";
                ss << "<td>" << sizefmt(a._fsize) << "</td>";
                // 添加以下内容，添加一个下载按钮，点击后跳转到文件的url，并且使用download属性让用户选择保存路径
                //ss << "<td><a href='" << a._url << "' class='download'>下载</a></td>";
                
                // 使用download属性，并且设置文件名为原始文件名
                ss << "<td><a href='" << a._url << "' class='download' download='" 
                << FileUtil(a._fpath).fileName() <<"'>下载</a></td>";
                
                // 结束添加
                ss << "</tr>";
            }
            
            ss << "</table></body></html>";

            //3、设置响应消息主体、头部信息、状态
            rsp.body = ss.str();
            rsp.set_header("Content-Type", "text/html; charset=UTF-8");
            rsp.status = 200;
        }





        static void download(const httplib::Request &req, httplib::Response &rsp){
            //1、根据资源路径req.path获取文件属性
            BackupInfo info;
            _data->getByURL(req.path, &info);
            
            //2、根据文件属性判断文件是否被压缩，如果被压缩要解压、删除压缩包、修改文件属性
            if(info._iscompressed==true){
                // 2.1、解压缩到指定目录
                FileUtil fu(info._ppath);
                fu.uncompress(info._fpath);
                //2.2、删除压缩包
                fu.remove();
                //2.3、修改文件属性
                info._iscompressed = false;
                _data->update(info);
            }

            

            //3、判断是否支持断点续传
            //如果没有If-Range字段或者If-Range的值与当前ETag不一致，则重新下载，否则断点续传
            bool retrans = false;
            string old_etag;
            if (req.has_header("If-Range")){
                old_etag = req.get_header_value("If-Range");
                if(old_etag == getETag(info)){
                    retrans = true;
                }
            }

            //4、读取文件数据
            FileUtil fu(info._fpath);
            if(retrans == false){
                //5、正常下载，读取文件数据进行，设置响应头部字段ETage、Accpet-Ranges
                fu.getContent(&rsp.body);
                rsp.set_header("Content-Type", "application/octet-stream");
                rsp.set_header("Accpet-Ranges", "bytes");
                rsp.set_header("ETag", getETag(info));
                rsp.status = 200;
            }else{
                //6、断点续传，httplib实现了断点续传，只需要用户将文件所有数据读取到rsp.body中
                //关键点：
                //  1、告诉服务器下载区间 -- Content-Range : 数据单位 起始位置-结束位置/文件大小
                //  2、服务器要能检测上一次下载后这个文件是否被修改过 -- If-Range（存放客户端收到的ETag）
                //      string range = req.get_header_value("Range"); bytes=start-end
                fu.getContent(&rsp.body);
                rsp.set_header("Content-Type", "application/octet-stream");
                rsp.set_header("Accpet-Ranges", "bytes");
                rsp.set_header("ETag", getETag(info));
                //      rsp.set_header("Conten-Range", "bytes start-end/fsize");
                rsp.status = 206;   //区间响应
            }

            

            
        }

        //静态成员函数中不能调用非静态成员函数，缺少this，这里要加上static
        static string timefmt(time_t time){
            return ctime(&time);
        }

        static string sizefmt(double size){
            size /= 1024;
            if(size > 1024){
                size /= 1024;
                return to_string(size) + " MB";
            }
            return to_string(size) + " KB";
        }

        static string getETag(const BackupInfo &info){
            //filename-fsize-lmtime
            FileUtil fu(info._fpath);
            string etag = fu.fileName();
            etag += "-";
            etag += to_string(info._fsize);
            etag += "-";
            etag += to_string(info._lmtime);
            return etag;
        }

    private:
        int _server_port;
        string _server_ip;
        string _download_prefix;
        httplib::Server _server;
    };
}


#endif